﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using WoWConnecter.Entities;

namespace WoWConnecter
{
    public enum PacketType
    {
        Authentication,
        Realm
    }

    public class BasicPacketIn : BinaryReader
    {
        public static Encoding DefaultEncoding = Encoding.UTF8;
        public virtual int HeaderLength { get { return 1; } }
        public virtual int SizeLength { get { return 2; } }

        public virtual bool HeaderRead
        {
            get
            {
                return ContentLength != -1;
            }
        }

        public virtual int ContentLength { get; internal set; }
        public virtual int Length
        {
            get { return (int)BaseStream.Length; }
        }

        public virtual int Position
        {
            get { return (int)BaseStream.Position; }
        }

        public int RawId { get; internal set; }

        public PacketType PacketType { get; internal set; }

        public DateTime TimeReceived { get; private set; }

        public BasicPacketIn()
            : base(new MemoryStream())
        {
            RawId = -1;
            TimeReceived = DateTime.Now;
            ContentLength = -1;
        }

        public virtual int Write(byte[] buffer, int offset, int length)
        {
            BaseStream.Write(buffer, offset, length);

            return length;
        }

        public virtual void ParseLength()
        {
            long position = BaseStream.Position;

            BaseStream.Position = 0;
            ContentLength = ReadUInt16BE();
        }

        public byte[] ToArray()
        {
            return (BaseStream as MemoryStream).ToArray();
        }

        public virtual void FinalizePacket()
        {
            BaseStream.Position = 0;
        }

        private bool EnsureData(int length)
        {
            if (Length - Position < length)
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Reads an EntityId from this stream and advances the current position of the stream by eight bytes.
        /// </summary>
        /// <returns>An EntityId read from the current stream.</returns>
        public EntityId ReadEntityId()
        {
            EnsureData(8);

            return new EntityId(ReadBytes(8));
        }

        /// <summary>
        /// Reads a 2-byte big endian integer value from the current stream and advances the current position of the stream by two bytes.
        /// </summary>
        /// <returns>A 2-byte big endian integer value read from the current stream.</returns>
        public ushort ReadUInt16BE()
        {
            return (ushort)((ReadByte() << 8) | ReadByte());
        }

        /// <summary>
        /// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes.
        /// </summary>
        /// <returns>A 4-byte floating point value read from the current stream.</returns>
        public float ReadFloat()
        {
            return ReadSingle();
        }

        /// <summary>
        /// Reads a null-terminated UTF-8 encoded string.
        /// </summary>
        /// <returns>the string that was read</returns>
        public string ReadCString()
        {
            byte tempByte;
            var chrBuffer = new List<byte>();

            while ((tempByte = ReadByte()) != 0)
            {
                chrBuffer.Add(tempByte);
            }

            return DefaultEncoding.GetString(chrBuffer.ToArray());
        }

        /// <summary>
        /// Reads a string from the current stream. The string is prefixed with the length, encoded as an integer seven bits at a time.
        /// </summary>
        /// <returns>The string being read.</returns>
        public string ReadPascalString()
        {
            int size = ReadByte();

            if (!EnsureData(size))
            {
                return "";
            }

            return new string(ReadChars(size));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns>The string being read.</returns>
        public string ReadPascalStringUShort()
        {
            int size = ReadUInt16();

            if (!EnsureData(size))
            {
                return "";
            }

            return new string(ReadChars(size));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns>The string being read.</returns>
        public string ReadPascalStringUInt()
        {
            var size = ReadInt32();

            if (!EnsureData(size))
            {
                return "";
            }

            return new string(ReadChars(size));
        }

        /// <summary>
        /// Reads a string from the current stream, and reverses it. The string read is of length bytes.
        /// </summary>
        /// <returns>The string being read.</returns>
        public string ReadReversedPascalString(int length)
        {
            if (length < 1)
            {
                throw new ArgumentOutOfRangeException("length", "string length must be greater than zero!");
            }

            if (!EnsureData(length))
            {
                return "";
            }

            char[] chrs = ReadChars(length);
            Utility.Reverse(chrs);

            return new string(chrs);
        }

        /// <summary>
        /// Reads a <see cref="BigInteger" /> from the current stream. The <see cref="BigInteger" /> is of length bytes.
        /// </summary>
        /// <param name="length">The length in bytes of the <see cref="BigInteger" />.</param>
        /// <returns>The <see cref="BigInteger" /> representation of the bytes.</returns>
        public BigInteger ReadBigInteger(int length)
        {
            if (length < 1)
            {
                throw new ArgumentOutOfRangeException("length", "BigInteger length must be greater than zero!");
            }

            if (!EnsureData(length))
            {
                return new BigInteger(0);
            }

            byte[] data = ReadBytes(length);

            if (data.Length < length)
            {
                return new BigInteger(0);
            }

            return new BigInteger(data);
        }

        /// <summary>
        /// Reads a <see cref="BigInteger" /> from the current stream. The <see cref="BigInteger" /> is prefixed by the length.
        /// </summary>
        /// <returns>The <see cref="BigInteger" /> representation of the bytes.</returns>
        public BigInteger ReadBigIntegerLengthValue()
        {
            byte length = ReadByte();

            return (length != 0 ? ReadBigInteger(length) : new BigInteger());
        }

        /// <summary>
        /// Reads an <see cref="XmlIPAddress" /> from the current stream. 
        /// </summary>
        /// <returns>The <see cref="XmlIPAddress" /> representation of the bytes.</returns>
        public XmlIPAddress ReadIPAddress()
        {
            if (!EnsureData(4))
            {
                return new XmlIPAddress();
            }

            byte[] data = ReadBytes(4);

            return new XmlIPAddress(data);
        }

        ///// <summary>
        ///// Reads a <see cref="Vector3" /> from the current stream. 
        ///// </summary>
        ///// <returns>The <see cref="Vector3" /> representation of the bytes.</returns>
        //public Vector3 ReadVector3()
        //{
        //    return new Vector3(ReadSingle(), ReadSingle(), ReadSingle());
        //}

        ///// <summary>
        ///// Reads a <see cref="Vector4" /> from the current stream. 
        ///// </summary>
        ///// <returns>The <see cref="Vector4" /> representation of the bytes.</returns>
        //public Vector4 ReadVector4()
        //{
        //    return new Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
        //}

        /// <summary>
        /// Reads an EntityId from this stream and advances the current position of the stream by eight bytes.
        /// </summary>
        /// <returns>An EntityId read from the current stream.</returns>
        //public EntityId ReadEntityId()
        //{
        //    EnsureData(8);

        //    return new EntityId(ReadBytes(8));
        //}

        ///// <summary>
        ///// Reads an EntitiyId packed
        ///// </summary>
        ///// <returns></returns>
        //public EntityId ReadPackedEntityId()
        //{
        //    byte mask = ReadByte();
        //    var guid = new byte[8];

        //    for (int i = 0; i < 8; i++)
        //    {
        //        if ((mask & (1 << i)) != 0)
        //        {
        //            guid[i] = ReadByte();
        //        }
        //    }

        //    return new EntityId(guid);
        //}

        /// <summary>
        /// Advances the position of the current stream by num bytes.
        /// </summary>
        /// <param name="num">The number of bytes to advance.</param>
        public void SkipBytes(int num)
        {
            BaseStream.Seek(num, SeekOrigin.Current);
        }
    }
}
